package com.framework.dailyviewactivity.weekview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Typeface;
import android.os.Build;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.OverScroller;

import androidx.annotation.Nullable;
import androidx.core.view.GestureDetectorCompat;
import androidx.core.view.ViewCompat;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;

import com.framework.dailyviewactivity.R;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

import static com.framework.dailyviewactivity.weekview.DiaryViewUtil.isSameDay;
import static com.framework.dailyviewactivity.weekview.DiaryViewUtil.today;


public class WeekView extends View {
    private enum Direction {
        NONE,
        LEFT,
        RIGHT,
        VERTICAL
    }

    @Deprecated
    public static final int LENGTH_SHORT = 1;
    @Deprecated
    public static final int LENGTH_LONG = 2;
    private final Context mContext;
    private Paint mTimeTextPaint;
    private float mTimeTextWidth;
    private float mTimeTextHeight;
    private Paint mHeaderTextPaint;
    private float mHeaderTextHeight;
    private float mHeaderHeight;
    private GestureDetectorCompat mGestureDetector;
    private OverScroller mScroller;
    private PointF mCurrentOrigin = new PointF(0f, 0f);
    private Direction mCurrentScrollDirection = Direction.NONE;
    private Paint mHeaderBackgroundPaint;
    private float mWidthPerDay;
    private Paint mDayBackgroundPaint;
    private Paint mHourSeparatorPaint;
    private float mHeaderMarginBottom;
    private Paint mTodayBackgroundPaint;
    private Paint mFutureBackgroundPaint;
    private Paint mPastBackgroundPaint;
    private Paint mFutureWeekendBackgroundPaint;
    private Paint mPastWeekendBackgroundPaint;
    private Paint mNowLinePaint;
    private Paint mTodayHeaderTextPaint;
    private Paint mDiaryBackgroundPaint, mDiaryStrokePaint;
    private float mHeaderColumnWidth;
    private List<DiaryRect> mDiaryRects;
    private List<? extends DiaryItemEntity> mPreviousPeriodDiarys;
    private List<? extends DiaryItemEntity> mCurrentPeriodDiarys;
    private List<? extends DiaryItemEntity> mNextPeriodDiarys;
    private TextPaint mDiaryTextPaint;
    private Paint mHeaderColumnBackgroundPaint;
    private int mFetchedPeriod = -1;
    private boolean mRefreshDiarys = false;
    private Direction mCurrentFlingDirection = Direction.NONE;
    private ScaleGestureDetector mScaleDetector;
    private boolean mIsZooming;
    private Calendar mFirstVisibleDay;
    private Calendar mLastVisibleDay;
    private boolean mShowFirstDayOfWeekFirst = false;
    private int mDefaultDiaryColor;
    private int mMinimumFlingVelocity = 0;
    private int mScaledTouchSlop = 0;
    private int mHourHeight = 50;
    private int mNewHourHeight = -1;
    private int mMinHourHeight = 0;
    private int mEffectiveMinHourHeight = mMinHourHeight;
    private int mMaxHourHeight = 250;
    private int mColumnGap = 10;
    private int mFirstDayOfWeek = Calendar.MONDAY;
    private int mTextSize = 12;
    private int mHeaderColumnPadding = 10;
    private int mHeaderColumnTextColor = Color.BLACK;
    private int mNumberOfVisibleDays = 1;
    private int mHeaderRowPadding = 10;
    private int mHeaderRowBackgroundColor = Color.WHITE;
    private int mDayBackgroundColor = Color.rgb(245, 245, 245);
    private int mPastBackgroundColor = Color.rgb(227, 227, 227);
    private int mFutureBackgroundColor = Color.rgb(245, 245, 245);
    private int mPastWeekendBackgroundColor = 0;
    private int mFutureWeekendBackgroundColor = 0;
    private int mNowLineColor = Color.rgb(102, 102, 102);
    private int mNowLineThickness = 5;
    private int mHourSeparatorColor = Color.rgb(230, 230, 230);
    private int mTodayBackgroundColor = Color.rgb(239, 247, 254);
    private int mHourSeparatorHeight = 2;
    private int mTodayHeaderTextColor = Color.rgb(39, 137, 228);
    private int mDiaryTextSize = 12;
    private int mDiaryTextColor = Color.BLACK;
    private int mDiaryPadding = 8;
    private int mHeaderColumnBackgroundColor = Color.WHITE;
    private boolean mIsFirstDraw = true;
    private boolean mAreDimensionsInvalid = true;
    private int mOverlappingDiaryGap = 0;
    private int mDiaryMarginVertical = 0;
    private float mXScrollingSpeed = 1f;
    private Calendar mScrollToDay = null;
    private double mScrollToHour = -1;
    private int mDiaryCornerRadius = 8;
    private boolean mShowDistinctWeekendColor = false;
    private boolean mShowNowLine = true;
    private boolean mShowDistinctPastFutureColor = false;
    private boolean mHorizontalFlingEnabled = false;
    private boolean mVerticalFlingEnabled = false;
    private int mAllDayDiaryHeight = 100;
    private int mScrollDuration = 250;


    private DiaryClickListener mDiaryClickListener;
    private DiaryLongPressListener mDiaryLongPressListener;
    private WeekViewLoader mWeekViewLoader;
    private EmptyViewClickListener mEmptyViewClickListener;
    private EmptyViewLongPressListener mEmptyViewLongPressListener;
    private DataInterpreter mDateTimeInterpreter;
    private ScrollListener mScrollListener;

    private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {

        @Override
        public boolean onDown(MotionEvent e) {
            goToNearestOrigin();
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (mIsZooming) {
                return true;
            }

            switch (mCurrentScrollDirection) {
                case NONE: {
                    if (Math.abs(distanceX) > Math.abs(distanceY)) {
                        if (distanceX > 0) {
                            if (mHorizontalFlingEnabled) {
                                mCurrentScrollDirection = Direction.LEFT;
                            }

                        } else {
                            if (mHorizontalFlingEnabled) {
                                mCurrentScrollDirection = Direction.RIGHT;
                            }
                        }
                    } else {
                        mCurrentScrollDirection = Direction.VERTICAL;
                    }
                    break;
                }
                case LEFT: {
                    // Change direction if there was enough change.
                    if (Math.abs(distanceX) > Math.abs(distanceY) && (distanceX < -mScaledTouchSlop)) {
                        mCurrentScrollDirection = Direction.RIGHT;
                    }
                    break;
                }
                case RIGHT: {
                    if (Math.abs(distanceX) > Math.abs(distanceY) && (distanceX > mScaledTouchSlop)) {
                        mCurrentScrollDirection = Direction.LEFT;
                    }
                    break;
                }
            }

            switch (mCurrentScrollDirection) {
                case LEFT:
                case RIGHT:
                    mCurrentOrigin.x -= distanceX * mXScrollingSpeed;
                    ViewCompat.postInvalidateOnAnimation(WeekView.this);
                    break;
                case VERTICAL:
                    mCurrentOrigin.y -= distanceY;
                    ViewCompat.postInvalidateOnAnimation(WeekView.this);
                    break;
            }
            return true;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (mIsZooming) {
                return true;
            }

            if ((mCurrentFlingDirection == Direction.LEFT && !mHorizontalFlingEnabled) ||
                    (mCurrentFlingDirection == Direction.RIGHT && !mHorizontalFlingEnabled) ||
                    (mCurrentFlingDirection == Direction.VERTICAL && !mVerticalFlingEnabled)) {
                return true;
            }

            mScroller.forceFinished(true);

            mCurrentFlingDirection = mCurrentScrollDirection;
            switch (mCurrentFlingDirection) {
                case LEFT:
                case RIGHT:
                    mScroller.fling((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, (int) (velocityX * mXScrollingSpeed), 0, Integer.MIN_VALUE, Integer.MAX_VALUE, (int) -(mHourHeight * 24 + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2 - getHeight()), 0);
                    break;
                case VERTICAL:
                    mScroller.fling((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, 0, (int) velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE, (int) -(mHourHeight * 24 + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2 - getHeight()), 0);
                    break;
            }

            ViewCompat.postInvalidateOnAnimation(WeekView.this);
            return true;
        }


        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            // If the tap was on an event then trigger the callback.
            if (mDiaryRects != null && mDiaryClickListener != null) {
                List<DiaryRect> reversedDiaryRects = mDiaryRects;
                Collections.reverse(reversedDiaryRects);
                for (DiaryRect event : reversedDiaryRects) {
                    if (event.rectF != null && e.getX() > event.rectF.left && e.getX() < event.rectF.right && e.getY() > event.rectF.top && e.getY() < event.rectF.bottom) {
                        mDiaryClickListener.onDiaryClick(event.originalDiary, event.rectF);
                        playSoundEffect(SoundEffectConstants.CLICK);
                        return super.onSingleTapConfirmed(e);
                    }
                }
            }

            if (mEmptyViewClickListener != null && e.getX() > mHeaderColumnWidth && e.getY() > (mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom)) {
                Calendar selectedTime = getTimeFromPoint(e.getX(), e.getY());
                if (selectedTime != null) {
                    playSoundEffect(SoundEffectConstants.CLICK);
                    mEmptyViewClickListener.onEmptyViewClicked(selectedTime);
                }
            }

            return super.onSingleTapConfirmed(e);
        }

        @Override
        public void onLongPress(MotionEvent e) {
            super.onLongPress(e);

            if (mDiaryLongPressListener != null && mDiaryRects != null) {
                List<DiaryRect> reversedDiaryRects = mDiaryRects;
                Collections.reverse(reversedDiaryRects);
                for (DiaryRect event : reversedDiaryRects) {
                    if (event.rectF != null && e.getX() > event.rectF.left && e.getX() < event.rectF.right && e.getY() > event.rectF.top && e.getY() < event.rectF.bottom) {
                        mDiaryLongPressListener.onDiaryLongPress(event.originalDiary, event.rectF);
                        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                        return;
                    }
                }
            }

            if (mEmptyViewLongPressListener != null && e.getX() > mHeaderColumnWidth && e.getY() > (mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom)) {
                Calendar selectedTime = getTimeFromPoint(e.getX(), e.getY());
                if (selectedTime != null) {
                    performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                    mEmptyViewLongPressListener.onEmptyViewLongPress(selectedTime);
                }
            }
        }
    };

    public WeekView(Context context) {
        this(context, null);
    }

    public WeekView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WeekView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // Hold references.
        mContext = context;

        // Get the attribute values (if any).
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.WeekView, 0, 0);
        try {
            mFirstDayOfWeek = a.getInteger(R.styleable.WeekView_firstDayOfWeek, mFirstDayOfWeek);
            mHourHeight = a.getDimensionPixelSize(R.styleable.WeekView_hourHeight, mHourHeight);
            mMinHourHeight = a.getDimensionPixelSize(R.styleable.WeekView_minHourHeight, mMinHourHeight);
            mEffectiveMinHourHeight = mMinHourHeight;
            mMaxHourHeight = a.getDimensionPixelSize(R.styleable.WeekView_maxHourHeight, mMaxHourHeight);
            mTextSize = a.getDimensionPixelSize(R.styleable.WeekView_textSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mTextSize, context.getResources().getDisplayMetrics()));
            mHeaderColumnPadding = a.getDimensionPixelSize(R.styleable.WeekView_headerColumnPadding, mHeaderColumnPadding);
            mColumnGap = a.getDimensionPixelSize(R.styleable.WeekView_columnGap, mColumnGap);
            mHeaderColumnTextColor = a.getColor(R.styleable.WeekView_headerColumnTextColor, mHeaderColumnTextColor);
            mNumberOfVisibleDays = a.getInteger(R.styleable.WeekView_noOfVisibleDays, mNumberOfVisibleDays);
            mShowFirstDayOfWeekFirst = a.getBoolean(R.styleable.WeekView_showFirstDayOfWeekFirst, mShowFirstDayOfWeekFirst);
            mHeaderRowPadding = a.getDimensionPixelSize(R.styleable.WeekView_headerRowPadding, mHeaderRowPadding);
            mHeaderRowBackgroundColor = a.getColor(R.styleable.WeekView_headerRowBackgroundColor, mHeaderRowBackgroundColor);
            mDayBackgroundColor = a.getColor(R.styleable.WeekView_dayBackgroundColor, mDayBackgroundColor);
            mFutureBackgroundColor = a.getColor(R.styleable.WeekView_futureBackgroundColor, mFutureBackgroundColor);
            mPastBackgroundColor = a.getColor(R.styleable.WeekView_pastBackgroundColor, mPastBackgroundColor);
            mFutureWeekendBackgroundColor = a.getColor(R.styleable.WeekView_futureWeekendBackgroundColor, mFutureBackgroundColor); // If not set, use the same color as in the week
            mPastWeekendBackgroundColor = a.getColor(R.styleable.WeekView_pastWeekendBackgroundColor, mPastBackgroundColor);
            mNowLineColor = a.getColor(R.styleable.WeekView_nowLineColor, mNowLineColor);
            mNowLineThickness = a.getDimensionPixelSize(R.styleable.WeekView_nowLineThickness, mNowLineThickness);
            mHourSeparatorColor = a.getColor(R.styleable.WeekView_hourSeparatorColor, mHourSeparatorColor);
            mTodayBackgroundColor = a.getColor(R.styleable.WeekView_todayBackgroundColor, mTodayBackgroundColor);
            mHourSeparatorHeight = a.getDimensionPixelSize(R.styleable.WeekView_hourSeparatorHeight, mHourSeparatorHeight);
            mTodayHeaderTextColor = a.getColor(R.styleable.WeekView_todayHeaderTextColor, mTodayHeaderTextColor);
            mDiaryTextSize = a.getDimensionPixelSize(R.styleable.WeekView_eventTextSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mDiaryTextSize, context.getResources().getDisplayMetrics()));
            mDiaryTextColor = a.getColor(R.styleable.WeekView_eventTextColor, mDiaryTextColor);
            mDiaryPadding = a.getDimensionPixelSize(R.styleable.WeekView_eventPadding, mDiaryPadding);
            mHeaderColumnBackgroundColor = a.getColor(R.styleable.WeekView_headerColumnBackground, mHeaderColumnBackgroundColor);
            mOverlappingDiaryGap = a.getDimensionPixelSize(R.styleable.WeekView_overlappingEventGap, mOverlappingDiaryGap);
            mDiaryMarginVertical = a.getDimensionPixelSize(R.styleable.WeekView_eventMarginVertical, mDiaryMarginVertical);
            mXScrollingSpeed = a.getFloat(R.styleable.WeekView_xScrollingSpeed, mXScrollingSpeed);
            mDiaryCornerRadius = a.getDimensionPixelSize(R.styleable.WeekView_eventCornerRadius, mDiaryCornerRadius);
            mShowDistinctPastFutureColor = a.getBoolean(R.styleable.WeekView_showDistinctPastFutureColor, mShowDistinctPastFutureColor);
            mShowDistinctWeekendColor = a.getBoolean(R.styleable.WeekView_showDistinctWeekendColor, mShowDistinctWeekendColor);
            mShowNowLine = a.getBoolean(R.styleable.WeekView_showNowLine, mShowNowLine);
            mHorizontalFlingEnabled = a.getBoolean(R.styleable.WeekView_horizontalFlingEnabled, mHorizontalFlingEnabled);
            mVerticalFlingEnabled = a.getBoolean(R.styleable.WeekView_verticalFlingEnabled, mVerticalFlingEnabled);
            mAllDayDiaryHeight = a.getDimensionPixelSize(R.styleable.WeekView_allDayEventHeight, mAllDayDiaryHeight);
            mScrollDuration = a.getInt(R.styleable.WeekView_scrollDuration, mScrollDuration);
        } finally {
            a.recycle();
        }

        init();
    }

    private void init() {
        mGestureDetector = new GestureDetectorCompat(mContext, mGestureListener);
        mScroller = new OverScroller(mContext, new FastOutLinearInInterpolator());

        mMinimumFlingVelocity = ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity();
        mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
        String textBounds= "00:00";
        // 测量垂直量
        mTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTimeTextPaint.setTextAlign(Paint.Align.RIGHT);
        mTimeTextPaint.setTextSize(mTextSize);
        mTimeTextPaint.setColor(mHeaderColumnTextColor);
        Rect rect = new Rect();

        mTimeTextPaint.getTextBounds(textBounds, 0, textBounds.length(), rect);
        mTimeTextHeight = rect.height();
        mHeaderMarginBottom = mTimeTextHeight / 2;
        initTextTimeWidth();

        // 测量水平量
        mHeaderTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHeaderTextPaint.setColor(mHeaderColumnTextColor);
        mHeaderTextPaint.setTextAlign(Paint.Align.CENTER);
        mHeaderTextPaint.setTextSize(mTextSize);
        mHeaderTextPaint.getTextBounds(textBounds, 0, textBounds.length(), rect);
        mHeaderTextHeight = rect.height();
        mHeaderTextPaint.setTypeface(Typeface.DEFAULT_BOLD);

        // 头部绘制
        mHeaderBackgroundPaint = new Paint();
        mHeaderBackgroundPaint.setColor(mHeaderRowBackgroundColor);

        // 日背景
        mDayBackgroundPaint = new Paint();
        mDayBackgroundPaint.setColor(mDayBackgroundColor);
        mFutureBackgroundPaint = new Paint();
        mFutureBackgroundPaint.setColor(mFutureBackgroundColor);
        mPastBackgroundPaint = new Paint();
        mPastBackgroundPaint.setColor(mPastBackgroundColor);
        mFutureWeekendBackgroundPaint = new Paint();
        mFutureWeekendBackgroundPaint.setColor(mFutureWeekendBackgroundColor);
        mPastWeekendBackgroundPaint = new Paint();
        mPastWeekendBackgroundPaint.setColor(mPastWeekendBackgroundColor);

        // 小时分割线
        mHourSeparatorPaint = new Paint();
        mHourSeparatorPaint.setStyle(Paint.Style.STROKE);
        mHourSeparatorPaint.setStrokeWidth(mHourSeparatorHeight);
        mHourSeparatorPaint.setColor(mHourSeparatorColor);

        // 当前时间线
        mNowLinePaint = new Paint();
        mNowLinePaint.setStrokeWidth(mNowLineThickness);
        mNowLinePaint.setColor(mNowLineColor);

        // 今日背景
        mTodayBackgroundPaint = new Paint();
        mTodayBackgroundPaint.setColor(mTodayBackgroundColor);

        // 今日标题
        mTodayHeaderTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTodayHeaderTextPaint.setTextAlign(Paint.Align.CENTER);
        mTodayHeaderTextPaint.setTextSize(mTextSize);
        mTodayHeaderTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
        mTodayHeaderTextPaint.setColor(mTodayHeaderTextColor);

        // 背景色
        mDiaryBackgroundPaint = new Paint();
        mDiaryBackgroundPaint.setColor(Color.rgb(174, 208, 238));

        mDiaryStrokePaint = new Paint();
        mDiaryStrokePaint.setStyle(Paint.Style.STROKE);
        mDiaryStrokePaint.setStrokeWidth(1);
        mDiaryStrokePaint.setColor(Color.parseColor("#999999"));


        // header颜色
        mHeaderColumnBackgroundPaint = new Paint();
        mHeaderColumnBackgroundPaint.setColor(mHeaderColumnBackgroundColor);

        // 文字大小颜色
        mDiaryTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);
        mDiaryTextPaint.setStyle(Paint.Style.FILL);
        mDiaryTextPaint.setColor(mDiaryTextColor);
        mDiaryTextPaint.setTextSize(mDiaryTextSize);

        // 事件颜色
        mDefaultDiaryColor = Color.parseColor("#999999");

        mScaleDetector = new ScaleGestureDetector(mContext, new ScaleGestureDetector.OnScaleGestureListener() {
            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
                mIsZooming = false;
            }

            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                mIsZooming = true;
                goToNearestOrigin();
                return true;
            }

            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                mNewHourHeight = Math.round(mHourHeight * detector.getScaleFactor());
                invalidate();
                return true;
            }
        });
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mAreDimensionsInvalid = true;
    }


    private void initTextTimeWidth() {
        mTimeTextWidth = 0;
        for (int i = 0; i < 24; i++) {
            // Measure time string and get max width.
            String time = getDateTimeInterpreter().interpretTime(i);
            if (time == null) {
                throw new IllegalStateException("必须返回时间");
            }
            mTimeTextWidth = Math.max(mTimeTextWidth, mTimeTextPaint.measureText(time));
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawHeaderRowAndDiarys(canvas);
        drawTimeColumnAndAxes(canvas);
    }

    private void calculateHeaderHeight() {
        //Make sure the header is the right size (depends on AllDay events)
        boolean containsAllDayDiary = false;
        if (mDiaryRects != null && mDiaryRects.size() > 0) {
            for (int dayNumber = 0;
                 dayNumber < mNumberOfVisibleDays;
                 dayNumber++) {
                Calendar day = (Calendar) getFirstVisibleDay().clone();
                day.add(Calendar.DATE, dayNumber);
                for (int i = 0; i < mDiaryRects.size(); i++) {

                    if (isSameDay(mDiaryRects.get(i).event.getStartTime(), day) && mDiaryRects.get(i).event.isAllDay()) {
                        containsAllDayDiary = true;
                        break;
                    }
                }
                if (containsAllDayDiary) {
                    break;
                }
            }
        }
        if (containsAllDayDiary) {
            mHeaderHeight = mHeaderTextHeight + (mAllDayDiaryHeight + mHeaderMarginBottom);
        } else {
            mHeaderHeight = mHeaderTextHeight;
        }
    }


    //绘制左侧时间
    private void drawTimeColumnAndAxes(Canvas canvas) {
        canvas.drawRect(0, mHeaderHeight + mHeaderRowPadding * 2, mHeaderColumnWidth, getHeight(), mHeaderColumnBackgroundPaint);
        canvas.clipRect(new RectF(0, mHeaderHeight + mHeaderRowPadding * 2, mHeaderColumnWidth, getHeight()), Region.Op.UNION);
        for (int i = 0; i < 24; i++) {
            float top = mHeaderHeight + mHeaderRowPadding * 2 + mCurrentOrigin.y + mHourHeight * i + mHeaderMarginBottom;

            String time = getDateTimeInterpreter().interpretTime(i);
            if (time == null) {
                throw new IllegalStateException("必须返回时间");
            }
            if (top < getHeight()) {
                canvas.drawText(time, mTimeTextWidth + mHeaderColumnPadding, top + mTimeTextHeight, mTimeTextPaint);
            }
        }
        canvas.save();
    }

    private void drawHeaderRowAndDiarys(Canvas canvas) {
        mHeaderColumnWidth = mTimeTextWidth + mHeaderColumnPadding * 2;
        mWidthPerDay = getWidth() - mHeaderColumnWidth - mColumnGap * (mNumberOfVisibleDays - 1);
        mWidthPerDay = mWidthPerDay / mNumberOfVisibleDays;
        calculateHeaderHeight();
        Calendar today = today();
        if (mAreDimensionsInvalid) {
            mEffectiveMinHourHeight = Math.max(mMinHourHeight, (int) ((getHeight() - mHeaderHeight - mHeaderRowPadding * 2 - mHeaderMarginBottom) / 24));
            mAreDimensionsInvalid = false;
            if (mScrollToDay != null) {
                goToDate(mScrollToDay);
            }
            mAreDimensionsInvalid = false;
            if (mScrollToHour >= 0) {
                goToHour(mScrollToHour);
            }
            mScrollToDay = null;
            mScrollToHour = -1;
            mAreDimensionsInvalid = false;
        }
        if (mIsFirstDraw) {
            mIsFirstDraw = false;
            if (mNumberOfVisibleDays >= 7 && today.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek && mShowFirstDayOfWeekFirst) {
                int difference = (today.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek);
                mCurrentOrigin.x += (mWidthPerDay + mColumnGap) * difference;
            }
        }
        if (mNewHourHeight > 0) {
            if (mNewHourHeight < mEffectiveMinHourHeight) {
                mNewHourHeight = mEffectiveMinHourHeight;
            } else if (mNewHourHeight > mMaxHourHeight) {
                mNewHourHeight = mMaxHourHeight;
            }

            mCurrentOrigin.y = (mCurrentOrigin.y / mHourHeight) * mNewHourHeight;
            mHourHeight = mNewHourHeight;
            mNewHourHeight = -1;
        }
        if (mCurrentOrigin.y < getHeight() - mHourHeight * 24 - mHeaderHeight - mHeaderRowPadding * 2 - mHeaderMarginBottom - mTimeTextHeight / 2) {
            mCurrentOrigin.y = getHeight() - mHourHeight * 24 - mHeaderHeight - mHeaderRowPadding * 2 - mHeaderMarginBottom - mTimeTextHeight / 2;
        }
        if (mCurrentOrigin.y > 0) {
            mCurrentOrigin.y = 0;
        }
        int leftDaysWithGaps = (int) -(Math.ceil(mCurrentOrigin.x / (mWidthPerDay + mColumnGap)));
        float startFromPixel = mCurrentOrigin.x + (mWidthPerDay + mColumnGap) * leftDaysWithGaps + mHeaderColumnWidth;
        float startPixel = startFromPixel;
        Calendar day = (Calendar) today.clone();
        day.add(Calendar.HOUR, 6);
        int lineCount = (int) ((getHeight() - mHeaderHeight - mHeaderRowPadding * 2 - mHeaderMarginBottom) / mHourHeight) + 1;
        lineCount = (lineCount) * (mNumberOfVisibleDays + 1);
        float[] hourLines = new float[lineCount * 4];
        if (mDiaryRects != null) {
            for (DiaryRect eventRect : mDiaryRects) {
                eventRect.rectF = null;
            }
        }
        canvas.clipRect(mHeaderColumnWidth, mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2, getWidth(), getHeight());
        Calendar oldFirstVisibleDay = mFirstVisibleDay;
        mFirstVisibleDay = (Calendar) today.clone();
        mFirstVisibleDay.add(Calendar.DATE, -(Math.round(mCurrentOrigin.x / (mWidthPerDay + mColumnGap))));
        if (!mFirstVisibleDay.equals(oldFirstVisibleDay) && mScrollListener != null) {
            mScrollListener.onFirstVisibleDayChanged(mFirstVisibleDay, oldFirstVisibleDay);
        }
        for (int dayNumber = leftDaysWithGaps + 1;
             dayNumber <= leftDaysWithGaps + mNumberOfVisibleDays + 1;
             dayNumber++) {

            day = (Calendar) today.clone();
            mLastVisibleDay = (Calendar) day.clone();
            day.add(Calendar.DATE, dayNumber - 1);
            mLastVisibleDay.add(Calendar.DATE, dayNumber - 2);
            boolean sameDay = isSameDay(day, today);

            if (mDiaryRects == null || mRefreshDiarys ||
                    (dayNumber == leftDaysWithGaps + 1 && mFetchedPeriod != (int) mWeekViewLoader.toWeekViewPeriodIndex(day) &&
                            Math.abs(mFetchedPeriod - mWeekViewLoader.toWeekViewPeriodIndex(day)) > 0.5)) {
                getMoreDiarys(day);
                mRefreshDiarys = false;
            }

            float start = (startPixel < mHeaderColumnWidth ? mHeaderColumnWidth : startPixel);
            if (mWidthPerDay + startPixel - start > 0) {
                if (mShowDistinctPastFutureColor) {
                    boolean isWeekend = day.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || day.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY;
                    Paint pastPaint = isWeekend && mShowDistinctWeekendColor ? mPastWeekendBackgroundPaint : mPastBackgroundPaint;
                    Paint futurePaint = isWeekend && mShowDistinctWeekendColor ? mFutureWeekendBackgroundPaint : mFutureBackgroundPaint;
                    float startY = mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom + mCurrentOrigin.y;

                    if (sameDay) {
                        Calendar now = Calendar.getInstance();
                        float beforeNow = (now.get(Calendar.HOUR_OF_DAY) + now.get(Calendar.MINUTE) / 60.0f) * mHourHeight;
                        canvas.drawRect(start, startY, startPixel + mWidthPerDay, startY + beforeNow, pastPaint);
                        canvas.drawRect(start, startY + beforeNow, startPixel + mWidthPerDay, getHeight(), futurePaint);
                    } else if (day.before(today)) {
                        canvas.drawRect(start, startY, startPixel + mWidthPerDay, getHeight(), pastPaint);
                    } else {
                        canvas.drawRect(start, startY, startPixel + mWidthPerDay, getHeight(), futurePaint);
                    }
                } else {
                    canvas.drawRect(start, mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom, startPixel + mWidthPerDay, getHeight(), sameDay ? mTodayBackgroundPaint : mDayBackgroundPaint);
                }
            }

            // Prepare the separator lines for hours.
            int i = 0;
            for (int hourNumber = 0; hourNumber < 24; hourNumber++) {
                float top = mHeaderHeight + mHeaderRowPadding * 2 + mCurrentOrigin.y + mHourHeight * hourNumber + mTimeTextHeight / 2 + mHeaderMarginBottom;
                if (top > mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom - mHourSeparatorHeight && top < getHeight() && startPixel + mWidthPerDay - start > 0) {
                    hourLines[i * 4] = start;
                    hourLines[i * 4 + 1] = top;
                    hourLines[i * 4 + 2] = startPixel + mWidthPerDay;
                    hourLines[i * 4 + 3] = top;
                    i++;
                }
            }
            canvas.drawLines(hourLines, mHourSeparatorPaint);
            drawDiarys(day, startPixel, canvas);
            if (mShowNowLine && sameDay) {
                float startY = mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom + mCurrentOrigin.y;
                Calendar now = Calendar.getInstance();
                float beforeNow = (now.get(Calendar.HOUR_OF_DAY) + now.get(Calendar.MINUTE) / 60.0f) * mHourHeight;
                //绘制当前时间线
                int radius = 10;
                canvas.drawCircle(start + radius, startY + beforeNow, radius, mNowLinePaint);
                canvas.drawLine(start + radius * 2 + 10, startY + beforeNow, startPixel + mWidthPerDay, startY + beforeNow, mNowLinePaint);
            }

            startPixel += mWidthPerDay + mColumnGap;
        }
        canvas.clipRect(0, 0, mTimeTextWidth + mHeaderColumnPadding * 2, mHeaderHeight + mHeaderRowPadding * 2);
        canvas.drawRect(0, 0, mTimeTextWidth + mHeaderColumnPadding * 2, mHeaderHeight + mHeaderRowPadding * 2, mHeaderBackgroundPaint);
        canvas.clipRect(new RectF(mHeaderColumnWidth, 0, getWidth(), mHeaderHeight + mHeaderRowPadding * 2), Region.Op.UNION);
        canvas.drawRect(0, 0, getWidth(), mHeaderHeight + mHeaderRowPadding * 2, mHeaderBackgroundPaint);

        // Draw the header row texts.
        startPixel = startFromPixel;
        for (int dayNumber = leftDaysWithGaps + 1; dayNumber <= leftDaysWithGaps + mNumberOfVisibleDays + 1; dayNumber++) {
            // Check if the day is today.
            day = (Calendar) today.clone();
            day.add(Calendar.DATE, dayNumber - 1);
            boolean sameDay = isSameDay(day, today);

            // Draw the day labels.
            String dayLabel = getDateTimeInterpreter().interpretDate(day);
            if (dayLabel == null) {
                throw new IllegalStateException("A DateTimeInterpreter must not return null date");
            }
            //绘制title文字
            canvas.drawText(dayLabel, startPixel + mWidthPerDay / 2, mHeaderTextHeight + mHeaderRowPadding, sameDay ? mTodayHeaderTextPaint : mHeaderTextPaint);
            drawAllDayDiarys(day, startPixel, canvas);
            startPixel += mWidthPerDay + mColumnGap;
        }

    }


    private Calendar getTimeFromPoint(float x, float y) {
        int leftDaysWithGaps = (int) -(Math.ceil(mCurrentOrigin.x / (mWidthPerDay + mColumnGap)));
        float startPixel = mCurrentOrigin.x + (mWidthPerDay + mColumnGap) * leftDaysWithGaps +
                mHeaderColumnWidth;
        for (int dayNumber = leftDaysWithGaps + 1;
             dayNumber <= leftDaysWithGaps + mNumberOfVisibleDays + 1;
             dayNumber++) {
            float start = (startPixel < mHeaderColumnWidth ? mHeaderColumnWidth : startPixel);
            if (mWidthPerDay + startPixel - start > 0 && x > start && x < startPixel + mWidthPerDay) {
                Calendar day = today();
                day.add(Calendar.DATE, dayNumber - 1);
                float pixelsFromZero = y - mCurrentOrigin.y - mHeaderHeight
                        - mHeaderRowPadding * 2 - mTimeTextHeight / 2 - mHeaderMarginBottom;
                int hour = (int) (pixelsFromZero / mHourHeight);
                int minute = (int) (60 * (pixelsFromZero - hour * mHourHeight) / mHourHeight);
                day.add(Calendar.HOUR, hour);
                day.set(Calendar.MINUTE, minute);
                return day;
            }
            startPixel += mWidthPerDay + mColumnGap;
        }
        return null;
    }

    private void drawDiarys(Calendar date, float startFromPixel, Canvas canvas) {
        if (mDiaryRects != null && mDiaryRects.size() > 0) {
            for (int i = 0; i < mDiaryRects.size(); i++) {
                if (isSameDay(mDiaryRects.get(i).event.getStartTime(), date) && !mDiaryRects.get(i).event.isAllDay()) {

                    // Calculate top.
                    float top = mHourHeight * 24 * mDiaryRects.get(i).top / 1440 + mCurrentOrigin.y + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2 + mDiaryMarginVertical;

                    // Calculate bottom.
                    float bottom = mDiaryRects.get(i).bottom;
                    bottom = mHourHeight * 24 * bottom / 1440 + mCurrentOrigin.y + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2 - mDiaryMarginVertical;

                    // Calculate left and right.
                    float left = startFromPixel + mDiaryRects.get(i).left * mWidthPerDay;
                    if (left < startFromPixel) {
                        left += mOverlappingDiaryGap;
                    }
                    float right = left + mDiaryRects.get(i).width * mWidthPerDay;
                    if (right < startFromPixel + mWidthPerDay) {
                        right -= mOverlappingDiaryGap;
                    }

                    // Draw the event and the event name on top of it.
                    if (left < right &&
                            left < getWidth() &&
                            top < getHeight() &&
                            right > mHeaderColumnWidth &&
                            bottom > mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom
                    ) {
                        mDiaryRects.get(i).rectF = new RectF(left, top, right, bottom);
                        // 绘制边框
                        RectF rectFEdge = new RectF(left, top, right - 8, bottom);
                        canvas.drawRoundRect(rectFEdge, mDiaryCornerRadius, mDiaryCornerRadius, mDiaryStrokePaint);
                        // 绘制内容物
                        RectF rectF1 = new RectF(left, top, left + 12, bottom);
                        RectF rectF2 = new RectF(left + 10, top, left + 12, bottom);
                        mDiaryBackgroundPaint.setColor(mDiaryRects.get(i).event.getColor() == 0 ? mDefaultDiaryColor : mDiaryRects.get(i).event.getColor());
                        canvas.drawRoundRect(rectF1, mDiaryCornerRadius, mDiaryCornerRadius, mDiaryBackgroundPaint);
                        canvas.drawRect(rectF2, mDiaryBackgroundPaint);
                        //绘制文字
                        int padding = 8;
                        drawDiaryTitle(mDiaryRects.get(i).event, mDiaryRects.get(i).rectF, canvas, top + padding, left + padding);
                    } else {
                        mDiaryRects.get(i).rectF = null;
                    }
                }
            }
        }
    }

    // 绘制跨天
    private void drawAllDayDiarys(Calendar date, float startFromPixel, Canvas canvas) {
        if (mDiaryRects != null && mDiaryRects.size() > 0) {
            for (int i = 0; i < mDiaryRects.size(); i++) {
                if (isSameDay(mDiaryRects.get(i).event.getStartTime(), date) && mDiaryRects.get(i).event.isAllDay()) {

                    // Calculate top.
                    float top = mHeaderRowPadding * 2 + mHeaderMarginBottom + +mTimeTextHeight / 2 + mDiaryMarginVertical;

                    // Calculate bottom.
                    float bottom = top + mDiaryRects.get(i).bottom;

                    // Calculate left and right.
                    float left = startFromPixel + mDiaryRects.get(i).left * mWidthPerDay;
                    if (left < startFromPixel) {
                        left += mOverlappingDiaryGap;
                    }
                    float right = left + mDiaryRects.get(i).width * mWidthPerDay;
                    if (right < startFromPixel + mWidthPerDay) {
                        right -= mOverlappingDiaryGap;
                    }

                    // Draw the event and the event name on top of it.
                    if (left < right &&
                            left < getWidth() &&
                            top < getHeight() &&
                            right > mHeaderColumnWidth &&
                            bottom > 0
                    ) {
                        mDiaryRects.get(i).rectF = new RectF(left, top, right, bottom);
                        mDiaryBackgroundPaint.setColor(mDiaryRects.get(i).event.getColor() == 0 ? mDefaultDiaryColor : mDiaryRects.get(i).event.getColor());
//                        canvas.drawRoundRect(mDiaryRects.get(i).rectF, mDiaryCornerRadius, mDiaryCornerRadius, mDiaryBackgroundPaint);

                        RectF rectFEdge = new RectF(left, top, right - 8, bottom);
                        canvas.drawRoundRect(rectFEdge, mDiaryCornerRadius, mDiaryCornerRadius, mDiaryStrokePaint);
                        RectF rectF1 = new RectF(left, top, left + 12, bottom);
                        RectF rectF2 = new RectF(left + 10, top, left + 12, bottom);
                        mDiaryBackgroundPaint.setColor(mDiaryRects.get(i).event.getColor() == 0 ? mDefaultDiaryColor : mDiaryRects.get(i).event.getColor());
                        canvas.drawRoundRect(rectF1, mDiaryCornerRadius, mDiaryCornerRadius, mDiaryBackgroundPaint);
                        canvas.drawRect(rectF2, mDiaryBackgroundPaint);
                        int padding = 8;
                        drawDiaryTitle(mDiaryRects.get(i).event, mDiaryRects.get(i).rectF, canvas, mDiaryRects.get(i).rectF.bottom / 2 + 12, left + padding);
                    } else {
                        mDiaryRects.get(i).rectF = null;
                    }
                }
            }
        }
    }


    private void drawDiaryTitle(DiaryItemEntity event, RectF rect, Canvas canvas, float originalTop, float originalLeft) {
        if (rect.right - rect.left - mDiaryPadding * 2 < 0) {
            return;
        }
        if (rect.bottom - rect.top - mDiaryPadding * 2 < 0) {
            return;
        }

        // Prepare the name of the event.
        SpannableStringBuilder bob = new SpannableStringBuilder();
        if (event.getName() != null) {
            bob.append(event.getName());
            bob.setSpan(new StyleSpan(Typeface.NORMAL), 0, bob.length(), 0);
            bob.append(' ');
        }

        // Prepare the location of the event.
        if (event.getLocation() != null) {
            bob.append(event.getLocation());
        }

        int availableHeight = (int) (rect.bottom - originalTop - mDiaryPadding * 2);
        int availableWidth = (int) (rect.right - originalLeft - mDiaryPadding * 2);

        // Get text dimensions.
        StaticLayout textLayout = new StaticLayout(bob, mDiaryTextPaint, availableWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);

        int lineHeight = textLayout.getHeight() / textLayout.getLineCount();

        if (availableHeight >= lineHeight) {
            // Calculate available number of line counts.
            int availableLineCount = availableHeight / lineHeight;
            do {
                // Ellipsize text to fit into event rect.
                textLayout = new StaticLayout(TextUtils.ellipsize(bob, mDiaryTextPaint, availableLineCount * availableWidth, TextUtils.TruncateAt.END), mDiaryTextPaint, (int) (rect.right - originalLeft - mDiaryPadding * 2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);

                // Reduce line count.
                availableLineCount--;

                // Repeat until text is short enough.
            } while (textLayout.getHeight() > availableHeight);

            // Draw text.
            canvas.save();
            canvas.translate(originalLeft + mDiaryPadding, originalTop + mDiaryPadding);
            textLayout.draw(canvas);
            canvas.restore();
        }
    }


    private class DiaryRect {
        public DiaryItemEntity event;
        public DiaryItemEntity originalDiary;
        public RectF rectF;
        public float left;
        public float width;
        public float top;
        public float bottom;

        public DiaryRect(DiaryItemEntity event, DiaryItemEntity originalDiary, RectF rectF) {
            this.event = event;
            this.rectF = rectF;
            this.originalDiary = originalDiary;
        }
    }

    private void getMoreDiarys(Calendar day) {

        if (mDiaryRects == null) {
            mDiaryRects = new ArrayList<>();
        }
        if (mWeekViewLoader == null && !isInEditMode()) {
            throw new IllegalStateException("必须先定义MonthChangeListener");
        }

        if (mRefreshDiarys) {
            mDiaryRects.clear();
            mPreviousPeriodDiarys = null;
            mCurrentPeriodDiarys = null;
            mNextPeriodDiarys = null;
            mFetchedPeriod = -1;
        }

        if (mWeekViewLoader != null) {
            int periodToFetch = (int) mWeekViewLoader.toWeekViewPeriodIndex(day);
            if (!isInEditMode() && (mFetchedPeriod < 0 || mFetchedPeriod != periodToFetch || mRefreshDiarys)) {
                List<? extends DiaryItemEntity> previousPeriodDiarys = null;
                List<? extends DiaryItemEntity> currentPeriodDiarys = null;
                List<? extends DiaryItemEntity> nextPeriodDiarys = null;

                if (mPreviousPeriodDiarys != null && mCurrentPeriodDiarys != null && mNextPeriodDiarys != null) {
                    if (periodToFetch == mFetchedPeriod - 1) {
                        currentPeriodDiarys = mPreviousPeriodDiarys;
                        nextPeriodDiarys = mCurrentPeriodDiarys;
                    } else if (periodToFetch == mFetchedPeriod) {
                        previousPeriodDiarys = mPreviousPeriodDiarys;
                        currentPeriodDiarys = mCurrentPeriodDiarys;
                        nextPeriodDiarys = mNextPeriodDiarys;
                    } else if (periodToFetch == mFetchedPeriod + 1) {
                        previousPeriodDiarys = mCurrentPeriodDiarys;
                        currentPeriodDiarys = mNextPeriodDiarys;
                    }
                }

                // 今日
                if (currentPeriodDiarys == null) {
                    currentPeriodDiarys = mWeekViewLoader.onLoad(periodToFetch);
                }
                //昨日
                if (previousPeriodDiarys == null) {
                    previousPeriodDiarys = mWeekViewLoader.onLoad(periodToFetch - 1);
                }
                //明日
                if (nextPeriodDiarys == null) {
                    nextPeriodDiarys = mWeekViewLoader.onLoad(periodToFetch + 1);
                }


                // Clear events.
                mDiaryRects.clear();
                sortAndCacheDiarys(previousPeriodDiarys);
                sortAndCacheDiarys(currentPeriodDiarys);
                sortAndCacheDiarys(nextPeriodDiarys);
                calculateHeaderHeight();

                mPreviousPeriodDiarys = previousPeriodDiarys;
                mCurrentPeriodDiarys = currentPeriodDiarys;
                mNextPeriodDiarys = nextPeriodDiarys;
                mFetchedPeriod = periodToFetch;
            }
        }

        List<DiaryRect> tempDiarys = mDiaryRects;
        mDiaryRects = new ArrayList<DiaryRect>();
        while (tempDiarys.size() > 0) {
            ArrayList<DiaryRect> eventRects = new ArrayList<>(tempDiarys.size());
            DiaryRect eventRect1 = tempDiarys.remove(0);
            eventRects.add(eventRect1);

            int i = 0;
            while (i < tempDiarys.size()) {
                DiaryRect eventRect2 = tempDiarys.get(i);
                if (isSameDay(eventRect1.event.getStartTime(), eventRect2.event.getStartTime())) {
                    tempDiarys.remove(i);
                    eventRects.add(eventRect2);
                } else {
                    i++;
                }
            }
            computePositionOfDiarys(eventRects);
        }
    }

    /**
     * Cache the event for smooth scrolling functionality.
     *
     * @param event The event to cache.
     */
    private void cacheDiary(DiaryItemEntity event) {
        if (event.getStartTime().compareTo(event.getEndTime()) >= 0) {
            return;
        }
        List<DiaryItemEntity> splitedDiarys = event.splitWeekViewDiarys();
        for (DiaryItemEntity splitedDiary : splitedDiarys) {
            mDiaryRects.add(new DiaryRect(splitedDiary, event, null));
        }
    }
    private void sortAndCacheDiarys(List<? extends DiaryItemEntity> events) {
        if (events == null) {
            return;
        }
        sortDiarys(events);
        for (DiaryItemEntity event : events) {
            cacheDiary(event);
        }
    }
    private void sortDiarys(List<? extends DiaryItemEntity> events) {
        if(events==null){
            return;
        }
        Collections.sort(events, new Comparator<DiaryItemEntity>() {
            @Override
            public int compare(DiaryItemEntity event1, DiaryItemEntity event2) {
                long start1 = event1.getStartTime().getTimeInMillis();
                long start2 = event2.getStartTime().getTimeInMillis();
                int comparator = start1 > start2 ? 1 : (start1 < start2 ? -1 : 0);
                if (comparator == 0) {
                    long end1 = event1.getEndTime().getTimeInMillis();
                    long end2 = event2.getEndTime().getTimeInMillis();
                    comparator = end1 > end2 ? 1 : (end1 < end2 ? -1 : 0);
                }
                return comparator;
            }
        });
    }

    private void computePositionOfDiarys(List<DiaryRect> eventRects) {
        // Make "collision groups" for all events that collide with others.
        List<List<DiaryRect>> collisionGroups = new ArrayList<List<DiaryRect>>();
        for (DiaryRect eventRect : eventRects) {
            boolean isPlaced = false;

            outerLoop:
            for (List<DiaryRect> collisionGroup : collisionGroups) {
                for (DiaryRect groupDiary : collisionGroup) {
                    if (isDiarysCollide(groupDiary.event, eventRect.event) && groupDiary.event.isAllDay() == eventRect.event.isAllDay()) {
                        collisionGroup.add(eventRect);
                        isPlaced = true;
                        break outerLoop;
                    }
                }
            }

            if (!isPlaced) {
                List<DiaryRect> newGroup = new ArrayList<DiaryRect>();
                newGroup.add(eventRect);
                collisionGroups.add(newGroup);
            }
        }

        for (List<DiaryRect> collisionGroup : collisionGroups) {
            expandDiarysToMaxWidth(collisionGroup);
        }
    }

    private void expandDiarysToMaxWidth(List<DiaryRect> collisionGroup) {
        // Expand the events to maximum possible width.
        List<List<DiaryRect>> columns = new ArrayList<List<DiaryRect>>();
        columns.add(new ArrayList<DiaryRect>());
        for (DiaryRect eventRect : collisionGroup) {
            boolean isPlaced = false;
            for (List<DiaryRect> column : columns) {
                if (column.size() == 0) {
                    column.add(eventRect);
                    isPlaced = true;
                } else if (!isDiarysCollide(eventRect.event, column.get(column.size() - 1).event)) {
                    column.add(eventRect);
                    isPlaced = true;
                    break;
                }
            }
            if (!isPlaced) {
                List<DiaryRect> newColumn = new ArrayList<DiaryRect>();
                newColumn.add(eventRect);
                columns.add(newColumn);
            }
        }

        int maxRowCount = 0;
        for (List<DiaryRect> column : columns) {
            maxRowCount = Math.max(maxRowCount, column.size());
        }
        for (int i = 0; i < maxRowCount; i++) {
            float j = 0;
            for (List<DiaryRect> column : columns) {
                if (column.size() >= i + 1) {
                    DiaryRect eventRect = column.get(i);
                    eventRect.width = 1f / columns.size();
                    eventRect.left = j / columns.size();
                    if (!eventRect.event.isAllDay()) {
                        eventRect.top = eventRect.event.getStartTime().get(Calendar.HOUR_OF_DAY) * 60 + eventRect.event.getStartTime().get(Calendar.MINUTE);
                        eventRect.bottom = eventRect.event.getEndTime().get(Calendar.HOUR_OF_DAY) * 60 + eventRect.event.getEndTime().get(Calendar.MINUTE);
                    } else {
                        eventRect.top = 0;
                        eventRect.bottom = mAllDayDiaryHeight;
                    }
                    mDiaryRects.add(eventRect);
                }
                j++;
            }
        }
    }
    private boolean isDiarysCollide(DiaryItemEntity event1, DiaryItemEntity event2) {
        long start1 = event1.getStartTime().getTimeInMillis();
        long end1 = event1.getEndTime().getTimeInMillis();
        long start2 = event2.getStartTime().getTimeInMillis();
        long end2 = event2.getEndTime().getTimeInMillis();
        return !((start1 >= end2) || (end1 <= start2));
    }


    private boolean isTimeAfterOrEquals(Calendar time1, Calendar time2) {
        return !(time1 == null || time2 == null) && time1.getTimeInMillis() >= time2.getTimeInMillis();
    }

    @Override
    public void invalidate() {
        super.invalidate();
        mAreDimensionsInvalid = true;
    }

    public void setOnDiaryClickListener(DiaryClickListener listener) {
        this.mDiaryClickListener = listener;
    }

    public DiaryClickListener getDiaryClickListener() {
        return mDiaryClickListener;
    }

    public @Nullable
    MonthLoader.MonthChangeListener getMonthChangeListener() {
        if (mWeekViewLoader instanceof MonthLoader) {
            return ((MonthLoader) mWeekViewLoader).getOnMonthChangeListener();
        }
        return null;
    }

    public void setMonthChangeListener(MonthLoader.MonthChangeListener monthChangeListener) {
        this.mWeekViewLoader = new MonthLoader(monthChangeListener);
    }
    public WeekViewLoader getWeekViewLoader() {
        return mWeekViewLoader;
    }
    public void setWeekViewLoader(WeekViewLoader loader) {
        this.mWeekViewLoader = loader;
    }

    public DiaryLongPressListener getDiaryLongPressListener() {
        return mDiaryLongPressListener;
    }

    public void setDiaryLongPressListener(DiaryLongPressListener eventLongPressListener) {
        this.mDiaryLongPressListener = eventLongPressListener;
    }

    public void setEmptyViewClickListener(EmptyViewClickListener emptyViewClickListener) {
        this.mEmptyViewClickListener = emptyViewClickListener;
    }

    public EmptyViewClickListener getEmptyViewClickListener() {
        return mEmptyViewClickListener;
    }

    public void setEmptyViewLongPressListener(EmptyViewLongPressListener emptyViewLongPressListener) {
        this.mEmptyViewLongPressListener = emptyViewLongPressListener;
    }

    public EmptyViewLongPressListener getEmptyViewLongPressListener() {
        return mEmptyViewLongPressListener;
    }

    public void setScrollListener(ScrollListener scrolledListener) {
        this.mScrollListener = scrolledListener;
    }

    public ScrollListener getScrollListener() {
        return mScrollListener;
    }
    public DataInterpreter getDateTimeInterpreter() {
        if (mDateTimeInterpreter == null) {
            mDateTimeInterpreter = new DataInterpreter() {
                @Override
                public String interpretDate(Calendar date) {
                    try {
                        SimpleDateFormat sdf = new SimpleDateFormat("EEE M/dd", Locale.getDefault());
                        return sdf.format(date.getTime()).toUpperCase();
                    } catch (Exception e) {
                        e.printStackTrace();
                        return "";
                    }
                }

                @Override
                public String interpretTime(int hour) {
                    Calendar calendar = Calendar.getInstance();
                    calendar.set(Calendar.HOUR_OF_DAY, hour);
                    calendar.set(Calendar.MINUTE, 0);

                    try {
                        SimpleDateFormat sdf = DateFormat.is24HourFormat(getContext()) ? new SimpleDateFormat("HH:mm", Locale.getDefault()) : new SimpleDateFormat("hh a", Locale.getDefault());
                        return sdf.format(calendar.getTime());
                    } catch (Exception e) {
                        e.printStackTrace();
                        return "";
                    }
                }
            };
        }
        return mDateTimeInterpreter;
    }


    public void setDateTimeInterpreter(DataInterpreter dateTimeInterpreter) {
        this.mDateTimeInterpreter = dateTimeInterpreter;
        initTextTimeWidth();
    }
    public int getNumberOfVisibleDays() {
        return mNumberOfVisibleDays;
    }
    public void setNumberOfVisibleDays(int numberOfVisibleDays) {
        this.mNumberOfVisibleDays = numberOfVisibleDays;
        mCurrentOrigin.x = 0;
        mCurrentOrigin.y = 0;
        invalidate();
    }

    public int getHourHeight() {
        return mHourHeight;
    }

    public void setHourHeight(int hourHeight) {
        mNewHourHeight = hourHeight;
        invalidate();
    }

    public int getColumnGap() {
        return mColumnGap;
    }

    public void setColumnGap(int columnGap) {
        mColumnGap = columnGap;
        invalidate();
    }

    public int getFirstDayOfWeek() {
        return mFirstDayOfWeek;
    }


    public void setFirstDayOfWeek(int firstDayOfWeek) {
        mFirstDayOfWeek = firstDayOfWeek;
        invalidate();
    }

    public boolean isShowFirstDayOfWeekFirst() {
        return mShowFirstDayOfWeekFirst;
    }

    public void setShowFirstDayOfWeekFirst(boolean show) {
        mShowFirstDayOfWeekFirst = show;
    }

    public int getTextSize() {
        return mTextSize;
    }

    public void setTextSize(int textSize) {
        mTextSize = textSize;
        mTodayHeaderTextPaint.setTextSize(mTextSize);
        mHeaderTextPaint.setTextSize(mTextSize);
        mTimeTextPaint.setTextSize(mTextSize);
        invalidate();
    }

    public int getHeaderColumnPadding() {
        return mHeaderColumnPadding;
    }

    public void setHeaderColumnPadding(int headerColumnPadding) {
        mHeaderColumnPadding = headerColumnPadding;
        invalidate();
    }

    public int getHeaderColumnTextColor() {
        return mHeaderColumnTextColor;
    }

    public void setHeaderColumnTextColor(int headerColumnTextColor) {
        mHeaderColumnTextColor = headerColumnTextColor;
        mHeaderTextPaint.setColor(mHeaderColumnTextColor);
        mTimeTextPaint.setColor(mHeaderColumnTextColor);
        invalidate();
    }

    public int getHeaderRowPadding() {
        return mHeaderRowPadding;
    }

    public void setHeaderRowPadding(int headerRowPadding) {
        mHeaderRowPadding = headerRowPadding;
        invalidate();
    }

    public int getHeaderRowBackgroundColor() {
        return mHeaderRowBackgroundColor;
    }

    public void setHeaderRowBackgroundColor(int headerRowBackgroundColor) {
        mHeaderRowBackgroundColor = headerRowBackgroundColor;
        mHeaderBackgroundPaint.setColor(mHeaderRowBackgroundColor);
        invalidate();
    }

    public int getDayBackgroundColor() {
        return mDayBackgroundColor;
    }

    public void setDayBackgroundColor(int dayBackgroundColor) {
        mDayBackgroundColor = dayBackgroundColor;
        mDayBackgroundPaint.setColor(mDayBackgroundColor);
        invalidate();
    }

    public int getHourSeparatorColor() {
        return mHourSeparatorColor;
    }

    public void setHourSeparatorColor(int hourSeparatorColor) {
        mHourSeparatorColor = hourSeparatorColor;
        mHourSeparatorPaint.setColor(mHourSeparatorColor);
        invalidate();
    }

    public int getTodayBackgroundColor() {
        return mTodayBackgroundColor;
    }

    public void setTodayBackgroundColor(int todayBackgroundColor) {
        mTodayBackgroundColor = todayBackgroundColor;
        mTodayBackgroundPaint.setColor(mTodayBackgroundColor);
        invalidate();
    }

    public int getHourSeparatorHeight() {
        return mHourSeparatorHeight;
    }

    public void setHourSeparatorHeight(int hourSeparatorHeight) {
        mHourSeparatorHeight = hourSeparatorHeight;
        mHourSeparatorPaint.setStrokeWidth(mHourSeparatorHeight);
        invalidate();
    }

    public int getTodayHeaderTextColor() {
        return mTodayHeaderTextColor;
    }

    public void setTodayHeaderTextColor(int todayHeaderTextColor) {
        mTodayHeaderTextColor = todayHeaderTextColor;
        mTodayHeaderTextPaint.setColor(mTodayHeaderTextColor);
        invalidate();
    }

    public int getDiaryTextSize() {
        return mDiaryTextSize;
    }

    public void setDiaryTextSize(int eventTextSize) {
        mDiaryTextSize = eventTextSize;
        mDiaryTextPaint.setTextSize(mDiaryTextSize);
        invalidate();
    }

    public int getDiaryTextColor() {
        return mDiaryTextColor;
    }

    public void setDiaryTextColor(int eventTextColor) {
        mDiaryTextColor = eventTextColor;
        mDiaryTextPaint.setColor(mDiaryTextColor);
        invalidate();
    }

    public int getDiaryPadding() {
        return mDiaryPadding;
    }

    public void setDiaryPadding(int eventPadding) {
        mDiaryPadding = eventPadding;
        invalidate();
    }

    public int getHeaderColumnBackgroundColor() {
        return mHeaderColumnBackgroundColor;
    }

    public void setHeaderColumnBackgroundColor(int headerColumnBackgroundColor) {
        mHeaderColumnBackgroundColor = headerColumnBackgroundColor;
        mHeaderColumnBackgroundPaint.setColor(mHeaderColumnBackgroundColor);
        invalidate();
    }

    public int getDefaultDiaryColor() {
        return mDefaultDiaryColor;
    }

    public void setDefaultDiaryColor(int defaultDiaryColor) {
        mDefaultDiaryColor = defaultDiaryColor;
        invalidate();
    }


    public int getOverlappingDiaryGap() {
        return mOverlappingDiaryGap;
    }


    public void setOverlappingDiaryGap(int overlappingDiaryGap) {
        this.mOverlappingDiaryGap = overlappingDiaryGap;
        invalidate();
    }

    public int getDiaryCornerRadius() {
        return mDiaryCornerRadius;
    }

    public void setDiaryCornerRadius(int eventCornerRadius) {
        mDiaryCornerRadius = eventCornerRadius;
    }

    public int getDiaryMarginVertical() {
        return mDiaryMarginVertical;
    }

    public void setDiaryMarginVertical(int eventMarginVertical) {
        this.mDiaryMarginVertical = eventMarginVertical;
        invalidate();
    }

    public Calendar getFirstVisibleDay() {
        return mFirstVisibleDay;
    }

    public Calendar getLastVisibleDay() {
        return mLastVisibleDay;
    }


    public float getXScrollingSpeed() {
        return mXScrollingSpeed;
    }

    public void setXScrollingSpeed(float xScrollingSpeed) {
        this.mXScrollingSpeed = xScrollingSpeed;
    }

    public boolean isShowDistinctWeekendColor() {
        return mShowDistinctWeekendColor;
    }

    public void setShowDistinctWeekendColor(boolean showDistinctWeekendColor) {
        this.mShowDistinctWeekendColor = showDistinctWeekendColor;
        invalidate();
    }

    public boolean isShowDistinctPastFutureColor() {
        return mShowDistinctPastFutureColor;
    }

    public void setShowDistinctPastFutureColor(boolean showDistinctPastFutureColor) {
        this.mShowDistinctPastFutureColor = showDistinctPastFutureColor;
        invalidate();
    }

    public boolean isShowNowLine() {
        return mShowNowLine;
    }

    public void setShowNowLine(boolean showNowLine) {
        this.mShowNowLine = showNowLine;
        invalidate();
    }

    public int getNowLineColor() {
        return mNowLineColor;
    }

    public void setNowLineColor(int nowLineColor) {
        this.mNowLineColor = nowLineColor;
        invalidate();
    }

    public int getNowLineThickness() {
        return mNowLineThickness;
    }

    public void setNowLineThickness(int nowLineThickness) {
        this.mNowLineThickness = nowLineThickness;
        invalidate();
    }

    public boolean isHorizontalFlingEnabled() {
        return mHorizontalFlingEnabled;
    }


    public void setHorizontalFlingEnabled(boolean enabled) {
        mHorizontalFlingEnabled = enabled;
    }

    public boolean isVerticalFlingEnabled() {
        return mVerticalFlingEnabled;
    }

    public void setVerticalFlingEnabled(boolean enabled) {
        mVerticalFlingEnabled = enabled;
    }

    public int getAllDayDiaryHeight() {
        return mAllDayDiaryHeight;
    }

    public void setAllDayDiaryHeight(int height) {
        mAllDayDiaryHeight = height;
    }

    public int getScrollDuration() {
        return mScrollDuration;
    }
    public void setScrollDuration(int scrollDuration) {
        mScrollDuration = scrollDuration;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mScaleDetector.onTouchEvent(event);
        boolean val = mGestureDetector.onTouchEvent(event);
        if (event.getAction() == MotionEvent.ACTION_UP && !mIsZooming && mCurrentFlingDirection == Direction.NONE) {
            if (mCurrentScrollDirection == Direction.RIGHT || mCurrentScrollDirection == Direction.LEFT) {
                goToNearestOrigin();
            }
            mCurrentScrollDirection = Direction.NONE;
        }

        return val;
    }

    private void goToNearestOrigin() {
        double leftDays = mCurrentOrigin.x / (mWidthPerDay + mColumnGap);

        if (mCurrentFlingDirection != Direction.NONE) {
            // snap to nearest day
            leftDays = Math.round(leftDays);
        } else if (mCurrentScrollDirection == Direction.LEFT) {
            // snap to last day
            leftDays = Math.floor(leftDays);
        } else if (mCurrentScrollDirection == Direction.RIGHT) {
            // snap to next day
            leftDays = Math.ceil(leftDays);
        } else {
            // snap to nearest day
            leftDays = Math.round(leftDays);
        }

        int nearestOrigin = (int) (mCurrentOrigin.x - leftDays * (mWidthPerDay + mColumnGap));

        if (nearestOrigin != 0) {
            // Stop current animation.
            mScroller.forceFinished(true);
            // Snap to date.
            mScroller.startScroll((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, -nearestOrigin, 0, (int) (Math.abs(nearestOrigin) / mWidthPerDay * mScrollDuration));
            ViewCompat.postInvalidateOnAnimation(WeekView.this);
        }
        // Reset scrolling and fling direction.
        mCurrentScrollDirection = mCurrentFlingDirection = Direction.NONE;
    }


    @Override
    public void computeScroll() {
        super.computeScroll();

        if (mScroller.isFinished()) {
            if (mCurrentFlingDirection != Direction.NONE) {
                // Snap to day after fling is finished.
                goToNearestOrigin();
            }
        } else {
            if (mCurrentFlingDirection != Direction.NONE && forceFinishScroll()) {
                goToNearestOrigin();
            } else if (mScroller.computeScrollOffset()) {
                mCurrentOrigin.y = mScroller.getCurrY();
                mCurrentOrigin.x = mScroller.getCurrX();
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
    }

    private boolean forceFinishScroll() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            // current velocity only available since api 14
            return mScroller.getCurrVelocity() <= mMinimumFlingVelocity;
        } else {
            return false;
        }
    }

    public void goToToday() {
        Calendar today = Calendar.getInstance();
        goToDate(today);
    }

    public void goToDate(Calendar date) {
        mScroller.forceFinished(true);
        mCurrentScrollDirection = mCurrentFlingDirection = Direction.NONE;

        date.set(Calendar.HOUR_OF_DAY, 0);
        date.set(Calendar.MINUTE, 0);
        date.set(Calendar.SECOND, 0);
        date.set(Calendar.MILLISECOND, 0);

        if (mAreDimensionsInvalid) {
            mScrollToDay = date;
            return;
        }

        mRefreshDiarys = true;

        Calendar today = Calendar.getInstance();
        today.set(Calendar.HOUR_OF_DAY, 0);
        today.set(Calendar.MINUTE, 0);
        today.set(Calendar.SECOND, 0);
        today.set(Calendar.MILLISECOND, 0);

        long day = 1000L * 60L * 60L * 24L;
        long dateInMillis = date.getTimeInMillis() + date.getTimeZone().getOffset(date.getTimeInMillis());
        long todayInMillis = today.getTimeInMillis() + today.getTimeZone().getOffset(today.getTimeInMillis());
        long dateDifference = (dateInMillis / day) - (todayInMillis / day);
        mCurrentOrigin.x = -dateDifference * (mWidthPerDay + mColumnGap);
        invalidate();
    }

    public void notifyDatasetChanged() {
        mRefreshDiarys = true;
        invalidate();
    }

    public void goToHour(double hour) {
        if (mAreDimensionsInvalid) {
            mScrollToHour = hour;
            return;
        }

        int verticalOffset = 0;
        if (hour > 24) {
            verticalOffset = mHourHeight * 24;
        } else if (hour > 0) {
            verticalOffset = (int) (mHourHeight * hour);
        }

        if (verticalOffset > mHourHeight * 24 - getHeight() + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom) {
            verticalOffset = (int) (mHourHeight * 24 - getHeight() + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom);
        }

        mCurrentOrigin.y = -verticalOffset;
        invalidate();
    }

    public double getFirstVisibleHour() {
        return -mCurrentOrigin.y / mHourHeight;
    }

    public interface DiaryClickListener {
        void onDiaryClick(DiaryItemEntity event, RectF eventRect);
    }

    public interface DiaryLongPressListener {
        void onDiaryLongPress(DiaryItemEntity event, RectF eventRect);
    }

    public interface EmptyViewClickListener {
        void onEmptyViewClicked(Calendar time);
    }

    public interface EmptyViewLongPressListener {
        void onEmptyViewLongPress(Calendar time);
    }

    public interface ScrollListener {
        void onFirstVisibleDayChanged(Calendar newFirstVisibleDay, Calendar oldFirstVisibleDay);
    }
}
